using System;
using System.Configuration;
using LegoBotPrimaAbstractions.Extensions;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;

namespace LegoBotPrimaAbstractions.Unity
{
    public abstract class UnityExtender<TContract> : IUnityExtender where TContract : IUnityExtender
    {
        /// <summary>
        /// Register the types with the passed container by implementing this function.
        /// </summary>
        /// <param name="container"></param>
        public virtual void RegisterTypes(IUnityContainer container)
        {
        }

        /// <summary>
        /// Array of Child Container Block Names from Unity Congfigurations.
        /// Usually this is just one string. E.g.: "MyApp"
        /// If you don't have any, then return null.
        /// </summary>
        /// <returns></returns>
        protected abstract string[] GetUnityConfigBlockNames();

        protected static TContract Init(TContract instance)
        {
            var baseContainer = new UnityContainer();
            instance.RegisterTypes(baseContainer);
            instance.Container = baseContainer.CreateChildContainer();
            instance.ContainerInformation = new UnityContainerInfo();
            instance.Container.AddExtension(instance.ContainerInformation);
            instance.ApplyConfiguration();
            return instance;
        }

        public IUnityContainer Container { get; set; }

        public UnityContainerInfo ContainerInformation { get; set; }

        public LifetimeManager Singleton { get { return new ContainerControlledLifetimeManager(); } }

        public T Resolve<T>()
        {
            return Container.Resolve<T>();
        }

        public T Resolve<T>(string name)
        {
            return Container.Resolve<T>(name);
        }

        public void ApplyConfiguration()
        {
            string[] containerBlocks = GetUnityConfigBlockNames();

            if (containerBlocks != null && containerBlocks.Length > 0)
            {
                var section = ConfigurationManager.GetSection("unity") as UnityConfigurationSection;

                if (section != null)
                {
                    foreach (string containerBlock in containerBlocks)
                    {
                        var current = section.Containers[containerBlock];

                        if (current != null)
                        {
                            DynamicConfigure(current, "App_Code");
                        }
                    }
                }
            }
        }



        public T ResolveOrDefault<T>()
        {
            return ContainerInformation.HasTypeWithoutName<T>() ? Container.Resolve<T>() : default(T);
        }
        public T ResolveOrDefault<T>(string name)
        {
            return ContainerInformation.HasTypeWithName<T>(name) ? Container.Resolve<T>(name) : default(T);
        }


        public object EnsureTypeAndResolve(Type type)
        {
            return EnsureTypeAndResolve(type, null);
        }
        public object EnsureTypeAndResolve(Type type, LifetimeManager lifetimeManager)
        {
            if (!ContainerInformation.HasTypeWithoutName(type))
            {
                if (type.IsAbstract || type.IsInterface)
                {
                    throw new ApplicationException("Type {0} passed to EnsureTypeAndResolve cannot be interface or abstract".Args(type));
                }
                Container.RegisterType(type, type, lifetimeManager);
            }
            return Container.Resolve(type);
        }

        public object ResolveLastRegisteredConcrete(Type concreteType)
        {
            var info = ContainerInformation.GetLastInfoByConcreteType(concreteType);
            return info == null ? null : Container.Resolve(info.TypeTo, info.Name);
        }

        public object ResolveLastRegisteredAbstract(Type abstractType)
        {
            var info = ContainerInformation.GetLastInfoByAbstractType(abstractType);
            return info == null ? null : Container.Resolve(info.TypeTo, info.Name);
        }
        

        private void DynamicConfigure(UnityContainerElement current,  string dynAssemblyName)
        {
            foreach (UnityContainerExtensionElement extension in current.Extensions)
            {
                extension.Configure(this.Container);
            }

            foreach (UnityTypeElement typeElement in current.Types)
            {
                var name = typeElement.MapToName.Replace(" ", "").Trim();
                dynAssemblyName = "," + dynAssemblyName.Trim();
                if (name.EndsWith(dynAssemblyName))
                {
                    name = name.Replace(dynAssemblyName, "");
                    var t = Type.GetType(name, false);
                    if (t != null)
                    {
                        this.Container.RegisterType(typeElement.Type, t, typeElement.Lifetime.CreateLifetimeManager());
                    }
                }
                else
                {
                    typeElement.Configure(this.Container);
                }
            }

            foreach (UnityInstanceElement instanceElement in current.Instances)
            {
                instanceElement.Configure(this.Container);
            }

            foreach (IContainerConfigurationCommand command in current.ExtensionConfig)
            {
                command.Configure(this.Container);
            }
        }
    }
}